﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using KL.UFrame.Core.Configuration;
using KL.UFrame.Core.Log;
using KL.UFrame.Core.Unity;
using KL.UFrame.Data.Business.Account;
using KL.UFrame.Data.Entities;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OAuth;

namespace KL.UFrame.Api.Providers
{
    public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
    {
        private readonly string _publicClientId;

        private ICommonLogger _logger;

        private IAccountBusiness _accountBusiness;

        private IOAuthBusiness _authBusiness;

        /// <summary>
        /// 可登录的角色组
        /// </summary>
        private string[] _clientRoles;

        public ApplicationOAuthProvider(string publicClientId)
        {
            if (publicClientId == null)
            {
                throw new ArgumentNullException(nameof(publicClientId));
            }

            _publicClientId = publicClientId;

            _logger = UnityBootstrapper.Current.GetService<ICommonLogger>();

            _accountBusiness = UnityBootstrapper.Current.GetService<IAccountBusiness>();

            _authBusiness= UnityBootstrapper.Current.GetService<IOAuthBusiness>();
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            ApplicationUser user;
            try
            {
                user = await _accountBusiness.FindUserAsync(context.UserName, context.Password);
            }
            catch (Exception ex)
            {
                _logger.Error("登录异常",ex);
                context.SetError("500", "服务暂不可用，请稍后再试");
                context.Response.StatusCode = 401;
                return;
            }

            if (user == null)
            {
                context.SetError("401", "用户名或密码不正确");
                context.Response.StatusCode = 401;
                return;
            }
            if (user.LockoutEnabled)
            {
                context.SetError("403", "用户已被锁定");
                context.Response.StatusCode = 401;
                return;
            }

            try
            {
                IIdentity id = (HttpContext.Current.User ?? Thread.CurrentPrincipal).Identity;
                if (id != null)
                {
                    var roles =  await _accountBusiness.GetUserRolesAsync(user.Id);

                    if (_clientRoles != null && _clientRoles.Length > 0)
                    {
                        if (!roles.Contains("系统管理员")  &&  !roles.Any(_clientRoles.Contains))
                        {
                            throw new Exception($"用户没有访问此客户端的权限：UserName={context.UserName}");
                        }
                    }

                    IPrincipal iPrincipal = new GenericPrincipal(id, roles);
                    SetPrincipal(iPrincipal);
                }
            }
            catch (Exception ex)
            {
                _logger.Error("获取角色失败", ex);
                context.SetError("500", "您没有访问权限");
                context.Response.StatusCode = 403;
                return;
            }
          
            var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();

            ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
               OAuthDefaults.AuthenticationType);
            ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
                CookieAuthenticationDefaults.AuthenticationType);

            AuthenticationProperties properties = CreateProperties(user.UserName);
            AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
            context.Validated(ticket);
            context.Request.Context.Authentication.SignIn(cookiesIdentity);
        }

        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }

            return Task.FromResult<object>(null);
        }


        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            try
            {
                string clientId;
                string clientSecret;
                if (context.TryGetBasicCredentials(out clientId, out clientSecret))
                {
                    //若有必要，验证客户端
                    var client =await _authBusiness.FindAsync(clientId, clientSecret);

                    if (client == null)
                    {
                        throw new Exception("客户端认证失败");
                    }

                    _clientRoles = client.Roles?.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);

                    context.Validated(clientId);
                }
            }
            catch (Exception ex)
            {
                context.SetError("解析客户端Id失败", ex.Message);
            }
            await base.ValidateClientAuthentication(context);
        }
        public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
        {
            var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            var ticket = new AuthenticationTicket(oAuthIdentity, new AuthenticationProperties());
            context.Validated(ticket);
            return base.GrantClientCredentials(context);
        }

        public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
        {
            if (context.ClientId == _publicClientId)
            {
                Uri expectedRootUri = new Uri(context.Request.Uri, "/");

                if (expectedRootUri.AbsoluteUri == context.RedirectUri)
                {
                    context.Validated();
                }
            }

            return Task.FromResult<object>(null);
        }

        public static AuthenticationProperties CreateProperties(string userName)
        {
            IDictionary<string, string> data = new Dictionary<string, string>
            {
                { "userName", userName }
            };
            return new AuthenticationProperties(data);
        }

        private static void SetPrincipal(IPrincipal principal)
        {

            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }
        }

    }
}